package net.stuffrepos.bricknetcenter.gui.inventories;

import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.List;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import net.stuffrepos.bricknetcenter.database.DataManager;
import net.stuffrepos.bricknetcenter.database.LazyResultSet;
import net.stuffrepos.bricknetcenter.database.schema.Information;
import net.stuffrepos.bricknetcenter.database.schema.TransferObject;
import net.stuffrepos.bricknetcenter.util.Inflector;
import net.stuffrepos.bricknetcenter.util.gui.LazyResultSetListModel;
import net.stuffrepos.util.cache.CacheableValue;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author Eduardo H. Bogoni <eduardobogoni@gmail.com>
 */
public class ObjectPicker extends javax.swing.JDialog {

    private static final EnumMap<TransferObject.Type, Filter> typeMapping =
            new EnumMap<TransferObject.Type, Filter>(TransferObject.Type.class);

    static {
        SetFilter setFilter = new SetFilter();
        PartFilter partFilter = new PartFilter();
        typeMapping.put(TransferObject.Type.Set, setFilter);
        typeMapping.put(TransferObject.Type.Box, setFilter);
        typeMapping.put(TransferObject.Type.Catalogue, setFilter);
        typeMapping.put(TransferObject.Type.Instructions, setFilter);
        typeMapping.put(TransferObject.Type.Part, partFilter);

    }
    private static final Log log = LogFactory.getLog(ObjectPicker.class);

    /** Creates new form ObjectPicker */
    public ObjectPicker(java.awt.Frame parent, boolean modal) {
        super(parent, modal);
        initComponents();
        populateType();

        result.setModel(new DefaultListModel());

        type.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    updateResults();
                }
            }
        });

        filter.getDocument().addDocumentListener(new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent e) {
                updateResults();
            }

            @Override
            public void removeUpdate(DocumentEvent e) {
                updateResults();
            }

            @Override
            public void changedUpdate(DocumentEvent e) {
                updateResults();
            }
        });

        result.setPrototypeCellValue("Index 0");

        updateResults();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        type = new javax.swing.JComboBox();
        categoryScrollPane = new javax.swing.JScrollPane();
        category = new javax.swing.JList();
        resultScrollPane = new javax.swing.JScrollPane();
        result = new javax.swing.JList();
        filter = new javax.swing.JTextField();
        categoryLabel = new javax.swing.JLabel();
        resultLabel = new javax.swing.JLabel();
        typeLabel = new javax.swing.JLabel();
        filterLabel = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);

        categoryScrollPane.setViewportView(category);

        resultScrollPane.setViewportView(result);

        categoryLabel.setText("Categories");

        resultLabel.setText("Found");

        typeLabel.setText("Type");

        filterLabel.setText("Filter");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                        .addComponent(categoryScrollPane, javax.swing.GroupLayout.Alignment.LEADING, 0, 0, Short.MAX_VALUE)
                        .addComponent(type, javax.swing.GroupLayout.Alignment.LEADING, 0, 119, Short.MAX_VALUE))
                    .addComponent(categoryLabel)
                    .addComponent(typeLabel))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(resultLabel)
                    .addComponent(resultScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 343, Short.MAX_VALUE)
                    .addComponent(filterLabel)
                    .addComponent(filter, javax.swing.GroupLayout.DEFAULT_SIZE, 343, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(typeLabel)
                    .addComponent(filterLabel))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(type, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(filter, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(categoryLabel)
                    .addComponent(resultLabel))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(categoryScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 390, Short.MAX_VALUE)
                    .addComponent(resultScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 390, Short.MAX_VALUE))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JList category;
    private javax.swing.JLabel categoryLabel;
    private javax.swing.JScrollPane categoryScrollPane;
    private javax.swing.JTextField filter;
    private javax.swing.JLabel filterLabel;
    private javax.swing.JList result;
    private javax.swing.JLabel resultLabel;
    private javax.swing.JScrollPane resultScrollPane;
    private javax.swing.JComboBox type;
    private javax.swing.JLabel typeLabel;
    // End of variables declaration//GEN-END:variables

    private void populateType() {
        type.setModel(new DefaultComboBoxModel(TransferObject.Type.values()));
    }

    private <T> ListModel createModelFromLazyResultSet(final LazyResultSet<T> lazyResultSet) {
        return new LazyResultSetListModel(lazyResultSet);
    }

    private void updateResults() {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                try {
                    Filter filter = typeMapping.get((TransferObject.Type) type.getSelectedItem());
                    assert filter != null;
                    result.setModel(filter.getListModel(ObjectPicker.this.filter.getText()));
                    resultLabel.setText("Found: " + result.getModel().getSize());
                } catch (Exception ex) {
                    log.warn("Error on populate object picker with sets.", ex);
                }
            }
        });
    }

    private interface Filter {

        public ListModel getListModel(String filter);
    }

    private static class SetFilter implements Filter {

        private CacheableValue<List<Information>> setInformations = new CacheableValue<List<Information>>() {

            @Override
            protected List<Information> calculate() {
                try {
                    return DataManager.getInstance().getInformationManager().findAllInformations(Information.Type.Set).fetchAll();
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        };

        @Override
        public ListModel getListModel(String filter) {
            DefaultListModel model = new DefaultListModel();
            for (Information i : setInformations.getValue()) {
                if (StringUtils.indexOfIgnoreCase(i.getValue(), filter) >= 0) {
                    model.addElement(new SetResult(i));
                }
            }
            return model;
        }

        private class SetResult {

            private final Information information;

            public SetResult(Information information) {
                assert information != null;
                assert Information.Type.Set.equals(information.getType());
                this.information = information;
            }

            @Override
            public String toString() {
                StringBuilder b = new StringBuilder();
                b.append(information.getValue());
                Information name = information.getFirstChild(Information.Type.Name);
                if (name != null) {
                    b.append(": ").append(name.getValue());
                }
                String informationsString = informationsToString(
                        information,
                        new Information.Type[]{
                            Information.Type.Year, Information.Type.Parts, Information.Type.Minifigs
                        });
                if (informationsString != null) {
                    b.append(" [").append(informationsString).append("]");
                }

                return b.toString();
            }

            public String informationsToString(Information parent, Information.Type[] informationTypes) {
                List<String> parts = new LinkedList<String>();
                for (Information.Type type : informationTypes) {
                    String informationString = informationToString(parent, type);
                    if (informationString != null) {
                        parts.add(informationString);
                    }
                }

                if (parts.isEmpty()) {
                    return null;
                } else {
                    return StringUtils.join(parts, ", ");
                }
            }

            public String informationToString(Information parent, Information.Type informationType) {
                Information child = parent.getFirstChild(informationType);
                if (child != null) {
                    return new StringBuilder().append(Inflector.humanize(informationType.toString())).
                            append(": ").
                            append(child.getValue().isEmpty() ? "?" : child.getValue()).toString();

                } else {
                    return null;
                }
            }
        }
    }

    private static class PartFilter implements Filter {

        private CacheableValue<List<PartResult>> partInformations = new CacheableValue<List<PartResult>>() {

            @Override
            protected List<PartResult> calculate() {
                try {
                    Collection<Information> parts = DataManager.getInstance().getInformationManager().findAllInformations(Information.Type.Part).fetchAll();
                    List<PartResult> result = new ArrayList<PartResult>(parts.size());
                    for (Information i : parts) {
                        result.add(new PartResult(i));

                    }
                    return result;
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        };

        @Override
        public ListModel getListModel(String filter) {
            DefaultListModel model = new DefaultListModel();
            for (PartResult part : partInformations.getValue()) {
                if (StringUtils.indexOfIgnoreCase(part.toString(), filter) >= 0) {
                    model.addElement(part);
                }
            }
            return model;
        }

        private static class PartResult {

            private CacheableValue<String> label = new CacheableValue<String>() {

                @Override
                protected String calculate() {
                    StringBuilder b = new StringBuilder();
                    b.append(information.getValue());
                    Information name = information.getFirstChild(Information.Type.Name);
                    if (name != null) {
                        b.append(": ").append(name.getValue());
                    }
                    return b.toString();
                }
            };
            private Information information;

            public PartResult(Information partInformation) {
                this.information = partInformation;
            }

            @Override
            public String toString() {

                return label.getValue();
            }
        }
    }
}
